/*
 * commons is a based project implemented
 * Copyright (C) 2024 Jasmine
 */
package com.easy.log.aspect;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.easy.core.exception.GlobalErrorInfoException;
import com.easy.core.utils.JsonUtils;
import com.easy.log.anno.SysLog;
import com.easy.log.dao.SysLogDao;
import com.easy.log.domain.LogRecord;
import com.easy.log.utils.LogTypeEnum;
import com.easy.log.utils.SysLogUtils;
import com.fasterxml.jackson.core.JsonProcessingException;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

/**
 * 操作日志 切面处理
 *
 * @author Jasmine
 * @since 1.0.0
 */
@Aspect
@Component
public class SysLogAspect {
    private static final Logger log = LoggerFactory.getLogger(SysLogAspect.class);

    /**
     * 服务名称
     */
    @Value("${spring.application.name:default}")
    public String serviceName;

    private final SysLogDao sysLogDao;

    public SysLogAspect(@Autowired(required = false) SysLogDao sysLogDao) {
        this.sysLogDao = sysLogDao;
    }

    @Around("@annotation(sysLog)")
    public Object around(ProceedingJoinPoint point, SysLog sysLog) throws Throwable {
        // 记录开始时间
        long startTime = System.currentTimeMillis();

        MethodSignature methodSignature = (MethodSignature)point.getSignature();

        // 构建日志
        LogRecord logRecord = SysLogUtils.getSysLog();
        logRecord.setServiceName(serviceName);

        Method handlerMethod = methodSignature.getMethod();

        // 记录所属模块
        this.logModule(logRecord, handlerMethod);

        // 记录日志描述
        this.logDescription(logRecord, handlerMethod);

        // 获取类名
        String className = point.getTarget().getClass().getName();
        // 获取方法名
        String methodName = methodSignature.getName();
        // 设置方法名称 eg: com.cloud.upms.controller.SysMenuController.add()
        logRecord.setMethod(className + "." + methodName + "()");

        // 获得参数名和参数
        String[] parameterNames = methodSignature.getParameterNames();
        String paramsStr = "";
        if (ArrayUtils.isNotEmpty(parameterNames)) {
            Object[] args = point.getArgs();
            Map<String, Object> argsMap = new HashMap<>(8);
            for (int i = 0; i < args.length; i++) {
                argsMap.put(parameterNames[i], args[i]);
            }

            try {
                paramsStr = JsonUtils.getInstance().writeValueAsString(argsMap);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            if (sysLog.isSaveRequestData()) {
                logRecord.setRequestParams(paramsStr);
            }
        }

        Object returnValue = null;
        try {
            /*
             *执行被环绕的方法 执行此方法前，为环绕通知的前置操作
             */
            returnValue = point.proceed();
            /*
             * 执行后为环绕通知的后置操作
             */
        } catch (Throwable e) {
            String message = e.getMessage();
            if (e instanceof GlobalErrorInfoException) {
                GlobalErrorInfoException exception = (GlobalErrorInfoException)e;
                message = exception.getErrorInfo().getMessage();
            }

            logRecord.setType(LogTypeEnum.ERROR.getType());
            logRecord.setExceptionInfo(message);
            log.error("操作日志环绕通知执行proceed方法失败! " + "[类名: {}] [方法名: {}][描述: {}] [参数: {}][异常{}]", className, methodName,
                sysLog.desc(), paramsStr, message);
            throw e;
        } finally {
            try {
                logRecord.setResponseResult(JsonUtils.getInstance().writeValueAsString(returnValue));
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
            Long endTime = System.currentTimeMillis();
            logRecord.setCostTime((endTime - startTime));
            log.debug("操作日志环绕通知执行proceed方法![类名: {}] [方法名: {}][operator: {}] [args: {}]", className, methodName,
                sysLog.desc(), paramsStr);
            // 保存记录
            if (Objects.nonNull(sysLogDao)) {
                sysLogDao.add(logRecord);
            }
        }
        return returnValue;
    }

    /**
     * 记录模块
     *
     * @param logRecord 日志信息
     * @param handlerMethod 处理器方法
     */
    private void logModule(LogRecord logRecord, Method handlerMethod) {
        // 优先级： 接口方法上的 @SysLog(module = "模块") > 接口类上的 @SysLog(module = "模块") > @Tag(name = "模块") 内容

        // 例如：@Log(module = "部门管理") -> 部门管理
        SysLog methodLog = handlerMethod.getAnnotation(SysLog.class);
        if (Objects.nonNull(methodLog) && StringUtils.isNotBlank(methodLog.desc())) {
            logRecord.setModule(methodLog.module());
            return;
        }
        SysLog classLog = handlerMethod.getClass().getDeclaredAnnotation(SysLog.class);
        if (Objects.nonNull(classLog) && StringUtils.isNotBlank(classLog.desc())) {
            logRecord.setModule(classLog.module());
            return;
        }

        // 例如：@Tag(name = "部门管理") -> 部门管理
        Tag classTag = handlerMethod.getClass().getDeclaredAnnotation(Tag.class);
        if (Objects.nonNull(classTag) && StringUtils.isNotBlank(classTag.name())) {
            logRecord.setModule(classTag.name());
        } else {
            logRecord.setModule("请在该接口类上指定所属模块");
        }
    }

    /**
     * 记录描述
     *
     * @param logRecord 日志信息
     * @param handlerMethod 处理器方法
     */
    private void logDescription(LogRecord logRecord, Method handlerMethod) {
        // 优先级： 接口类上的 @SysLog(desc = "新增部门") > @Operation(summary="新增部门")

        // 例如：@Log("新增部门") -> 新增部门
        SysLog methodLog = handlerMethod.getAnnotation(SysLog.class);
        if (Objects.nonNull(methodLog) && StringUtils.isNotBlank(methodLog.desc())) {
            logRecord.setDesc(methodLog.desc());
            return;
        }

        // 例如：@Operation(summary="新增部门") -> 新增部门
        Operation methodOperation = handlerMethod.getAnnotation(Operation.class);
        if (Objects.nonNull(methodOperation) && StringUtils.isNotBlank(methodOperation.summary())) {
            logRecord.setDesc(methodOperation.summary());
        } else {
            logRecord.setDesc("请在该接口方法上指定日志描述");
        }
    }

}
